package org.eweb4j.util.xml;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.eweb4j.util.ClassUtil;
import org.eweb4j.util.ReflectUtil;
import org.eweb4j.util.StringUtil;
import org.eweb4j.util.xml.tag.XmlTag;
import org.eweb4j.util.xml.tag.XmlTagType;

public class BeanXMLReader implements XMLReader {
	private String beanName;
	private File file;
	private Hashtable<String, Class<?>> classes = new Hashtable<String, Class<?>>();

	public void setClass(String key, Class<?> clazz) {
		if (ClassUtil.isPojo(clazz)) {

			Field[] fields = clazz.getDeclaredFields();
			for (Field f : fields) {
				if (List.class.isAssignableFrom(f.getType())) {
					ParameterizedType pt = (ParameterizedType) f
							.getGenericType();
					Type type = pt.getActualTypeArguments()[0];

					Class<?> cls = ClassUtil.getPojoClass(type.toString()
							.replace("class ", ""));

					setClass(f.getName(), cls);

				} else {
					setClass(f.getName(), f.getType());
				}
			}

			this.classes.put(key, clazz);
		}
	}

	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}

	public File getFile() {
		return file;
	}

	public void setFile(File file) {
		this.file = file;
	}

	public BeanXMLReader(File file) {
		this.setFile(file);
	}

	public BeanXMLReader() {
	}

	@SuppressWarnings("unchecked")
	public <T> List<T> read() throws Exception {
		List<T> tList = new ArrayList<T>();
		T t = null;
		SAXReader reader = new SAXReader();
		Document doc = reader.read(this.file);
		// 列出beans下的所有bean元素节点
		String sub;
		if (this.beanName == null || this.beanName.trim().length() == 0)
			sub = BeanXMLConstant.SUBROOT_ELEMENT;
		else
			sub = this.beanName;

		List<?> list = doc.selectNodes("//" + BeanXMLConstant.ROOT_ELEMENT
				+ "/" + sub);
		for (Iterator<?> it = list.iterator(); it.hasNext();) {
			Element bean = (Element) it.next();
			// 进入递归
			t = (T) this.readRecursion(bean);
			tList.add(t);
		}

		return tList;
	}

	public <T> T readOne() throws Exception {
		T t = null;
		List<T> list = this.read();
		if (list != null) {
			t = list.get(0);
		}
		return t;
	}

	@SuppressWarnings("unchecked")
	private <T> T readRecursion(Element bean) throws Exception {
		Attribute xmlBean = bean.attribute(BeanXMLConstant.CLASS_PROPERTY);
		Class<T> clazz = null;
		if (xmlBean != null) {
			clazz = (Class<T>) Class.forName(xmlBean.getValue());
		} else {
			Element e = bean.element(BeanXMLConstant.CLASS_PROPERTY);
			if (e != null)
				clazz = (Class<T>) Class.forName(e.getText());
			else
				clazz = (Class<T>) this.classes.get(bean.getName());
		}

		T o = clazz.newInstance();
		ReflectUtil ru = new ReflectUtil(o);
		Field[] fields = ru.getFields();
		for (Field f : fields) {
			String n = f.getName();
			Method m = ru.getMethod("set" + StringUtil.toUpCaseFirst(n));
			if (m != null) {
				Annotation[] annotation = f.getAnnotations();
				if (annotation != null && annotation.length > 0) {
					for (Annotation anno : annotation) {
						XmlTag tag = (XmlTag) anno;
						String type = tag.type();
						String value = tag.value();
						boolean canRead = tag.canRead();
						if (canRead) {
							if (XmlTagType.attriType.equals(type)) {
								if ("clazz".equals(n)) {
									n = "class";
								}
								Attribute a = bean.attribute(n);
								if (a != null) {
									m.invoke(o, a.getData());
								}
							} else if (XmlTagType.classType.equals(type)) {
								Element el = bean.element(n);
								if (el != null) {
									if ("".equals(value) || value == null) {
										Attribute a = el
												.attribute(BeanXMLConstant.CLASS_PROPERTY);
										if (a != null)
											value = a.getValue();
										else
											value = this.classes.get(
													el.getName()).getName();
									}

									Object object = Class.forName(value)
											.newInstance();
									// 递归
									object = readRecursion(el);
									m.invoke(o, object);
								}
							} else if (XmlTagType.listClassType.equals(type)) {
								List<?> eList = bean.elements(n);
								if (eList != null) {
									List<Object> list = new ArrayList<Object>();
									for (Iterator<?> it = eList.iterator(); it
											.hasNext();) {
										Element e = (Element) it.next();
										if ("".equals(value)) {
											Attribute a = e
													.attribute(BeanXMLConstant.CLASS_PROPERTY);
											if (a != null)
												value = a.getValue();
											else
												value = this.classes.get(
														e.getName()).getName();
										}

										Object object = Class.forName(value)
												.newInstance();
										// 递归
										object = readRecursion(e);
										list.add(object);
									}
									m.invoke(o, list);
								}
							} else if (XmlTagType.listElementType.equals(type)) {
								List<?> eList = bean.elements(n);
								if (eList != null) {
									List<String> list = new ArrayList<String>();
									for (Iterator<?> it = eList.iterator(); it
											.hasNext();) {
										Element e = (Element) it.next();
										list.add(e.getText());
									}
									m.invoke(o, list);
								}
							} else if (XmlTagType.elementType.equals(type)) {
								Element a = bean.element(n);
								if (a != null) {
									if ("clazz".equals(n)) {
										n = "class";
									}
									if (a != null) {
										m.invoke(o, a.getData());
									}
								}
							} else if ("".equals(type)) {
								Element a = bean.element(n);
								if (a != null) {
									if ("clazz".equals(n)) {
										n = "class";
									}
									if (a != null) {
										m.invoke(o, a.getData());
									}
								}
							}
						}
					}
				} else {
					m.invoke(o, bean.element(n).getData());
				}
			}
		}

		return o;
	}
}
